from fastapi import FastAPI, HTTPException, status, Depends, Request, Form
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
from starlette.middleware.sessions import SessionMiddleware

from datetime import datetime, timedelta
from typing import List, Optional
import json
import os
import uuid
from auth import (
    User, UserCreate, UserLogin, get_user_by_username,
    verify_password, get_password_hash, load_users, save_users
)

# 初始化FastAPI应用
app = FastAPI(title="BBS 评论系统 with Session Auth")

# 配置模板
templates = Jinja2Templates(directory="templates")

# 添加Jinja2日期格式化过滤器（用于模板中的日期显示）
def datetimeformat(value, format="%Y-%m-%d %H:%M"):
    if isinstance(value, str):
        try:
            value = datetime.fromisoformat(value)
        except ValueError:
            return value  # 格式错误时返回原始值
    return value.strftime(format)

templates.env.filters['datetimeformat'] = datetimeformat

# 数据存储文件路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# 修改数据文件路径定义（替换原来的DATA_FILE和SESSIONS_FILE）
DATA_FILE = os.path.join(BASE_DIR, "bbs_data.json")
SESSIONS_FILE = os.path.join(BASE_DIR, "sessions.json")

# 会话密钥配置
SECRET_KEY = os.getenv("BBS_SECRET_KEY", "dev-secret-key-1234567890abcdef")
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY, session_cookie="bbs_session")


# 评论数据模型
class Comment(BaseModel):
    id: Optional[int] = None
    content: str
    author: str
    author_id: int
    parent_id: Optional[int] = None
    created_at: Optional[datetime] = None

# 新增：仅接收前端提交的字段（content + parent_id）
class CommentCreate(BaseModel):
    content: str
    parent_id: Optional[int] = None  # 可选，回复时才传

# 会话文件处理（带容错）
def init_sessions_file():
    if not os.path.exists(SESSIONS_FILE):
        with open(SESSIONS_FILE, 'w', encoding='utf-8') as f:
            json.dump({}, f, ensure_ascii=False)
    else:
        try:
            with open(SESSIONS_FILE, 'r', encoding='utf-8') as f:
                json.load(f)
        except json.JSONDecodeError:
            with open(SESSIONS_FILE, 'w', encoding='utf-8') as f:
                json.dump({}, f, ensure_ascii=False)


def save_session(session_id: str, user_id: int):
    init_sessions_file()  # 初始化会话文件（sessions.json）
    with open(SESSIONS_FILE, 'r+', encoding='utf-8') as f:
        try:
            sessions = json.load(f)  # 读取已有的会话数据
        except json.JSONDecodeError:
            sessions = {}
        

        # !!!记录会话过期时间：当前时间 + 24小时
        sessions[session_id] = {
            "user_id": user_id,  # 关联的用户ID
            "expires_at": (datetime.now() + timedelta(days=1)).isoformat()
        }
        
        # 写入更新后的会话数据
        f.seek(0)
        f.truncate()
        json.dump(sessions, f, ensure_ascii=False, default=str)


def get_user_from_session(session_id: str) -> Optional[User]:
    init_sessions_file()
    with open(SESSIONS_FILE, 'r', encoding='utf-8') as f:
        try:
            sessions = json.load(f)
        except json.JSONDecodeError:
            sessions = {}
        
        if session_id in sessions:
            session_data = sessions[session_id]
            try:
                # !!!! 校验会话是否过期：解析过期时间并与当前时间对比
                expires_at = datetime.fromisoformat(session_data["expires_at"])
                if expires_at > datetime.now():
                    # 未过期：返回关联的用户信息
                    users = load_users()
                    return next((u for u in users if u.id == session_data["user_id"]), None)
                else:
                    # 已过期：删除无效会话，避免冗余数据
                    del sessions[session_id]
                    with open(SESSIONS_FILE, 'w', encoding='utf-8') as f_update:
                        json.dump(sessions, f_update, ensure_ascii=False, default=str)
            except (ValueError, KeyError):
                return None
    return None


# 获取当前登录用户
def get_current_user(request: Request) -> Optional[User]:
    session_id = request.cookies.get("session_id")
    if session_id:
        return get_user_from_session(session_id)
    return None


# 评论数据处理（带容错）
def init_data_file():
    if not os.path.exists(DATA_FILE):
        with open(DATA_FILE, 'w', encoding='utf-8') as f:
            json.dump([], f, ensure_ascii=False)
    else:
        try:
            with open(DATA_FILE, 'r', encoding='utf-8') as f:
                json.load(f)
        except json.JSONDecodeError:
            with open(DATA_FILE, 'w', encoding='utf-8') as f:
                json.dump([], f, ensure_ascii=False)


def load_comments() -> List[Comment]:
    init_data_file()
    with open(DATA_FILE, 'r', encoding='utf-8') as f:
        try:
            comments_data = json.load(f)
            return [Comment(**item) for item in comments_data]
        except (json.JSONDecodeError, TypeError):
            return []


def save_comments(comments: List[Comment]):
    with open(DATA_FILE, 'w', encoding='utf-8') as f:
        comments_dict = [comment.dict() for comment in comments]
        json.dump(comments_dict, f, ensure_ascii=False, default=str)


# 页面路由
@app.get("/register", response_class=HTMLResponse)
def register_page(request: Request, current_user: Optional[User] = Depends(get_current_user)):
    if current_user:
        return RedirectResponse(url="/", status_code=303)
    return templates.TemplateResponse("register.html", {"request": request})


@app.post("/register", response_class=HTMLResponse)
def handle_register(
        request: Request,
        username: str = Form(...),
        password: str = Form(...)
):
    if get_user_by_username(username):
        return templates.TemplateResponse("register.html", {
            "request": request,
            "error": "用户名已存在，请更换其他用户名"
        })

    users = load_users()
    new_user_id = max([u.id for u in users], default=0) + 1
    hashed_password = get_password_hash(password)

    new_user = User(
        id=new_user_id,
        username=username,
        password=hashed_password,
        created_at=datetime.now()
    )

    users.append(new_user)
    save_users(users)
    return RedirectResponse(url="/login?registered=true", status_code=303)


@app.get("/login", response_class=HTMLResponse)
def login_page(
        request: Request,
        registered: Optional[bool] = False,
        current_user: Optional[User] = Depends(get_current_user)
):
    if current_user:
        return RedirectResponse(url="/", status_code=303)
    return templates.TemplateResponse("login.html", {
        "request": request,
        "registered": registered
    })


@app.post("/login", response_class=HTMLResponse)
def handle_login(
        request: Request,
        username: str = Form(...),
        password: str = Form(...)
):
    # ... 用户名密码验证逻辑 ...
    user = get_user_by_username(username)
    if not user or not verify_password(password, user.password):
        return templates.TemplateResponse("login.html", {
            "request": request,
            "error": "用户名或密码错误，请重新输入"
        })

     # 生成唯一会话ID
    session_id = str(uuid.uuid4())
    # 保存会话（关联用户ID和过期时间）
    save_session(session_id, user.id)


    # 设置Cookie，关键参数控制时效
    response = RedirectResponse(url="/", status_code=303)
    response.set_cookie(
        key="session_id",  # 会话标识Cookie的key
        value=session_id,  # 会话ID值
        httponly=True,     # 防止JS读取，提升安全性
        max_age=86400,     # !!!登录时效核心：Cookie有效期=24小时（86400秒）
        secure=False,      # 开发环境关闭HTTPS校验（生产环境需设为True）
        samesite="lax"     # 限制跨站请求，提升安全性
    )
    return response


@app.get("/logout", response_class=HTMLResponse)
def handle_logout(request: Request):
    response = RedirectResponse(url="/", status_code=303)
    response.delete_cookie(key="session_id")
    return response


@app.get("/", response_class=HTMLResponse)
def home_page(request: Request, current_user: Optional[User] = Depends(get_current_user)):
    comments = load_comments()
    return templates.TemplateResponse("index.html", {
        "request": request,
        "comments": comments,
        "current_user": current_user
    })


# API路由
@app.get("/comments", response_model=List[Comment])
def api_get_comments():
    return load_comments()


@app.post("/comments", response_model=Comment, status_code=status.HTTP_201_CREATED)
def api_create_comment(
        comment: CommentCreate,
        current_user: Optional[User] = Depends(get_current_user)
):
    if not current_user:
        raise HTTPException(status_code=401, detail="请先登录再发表评论")

    comments = load_comments()
    new_comment_id = max([c.id for c in comments], default=0) + 1

 # 用 Comment 模型构造响应数据（后端填充 author/author_id/created_at）
    new_comment = Comment(
        id=new_comment_id,
        content=comment.content,  # 来自前端提交
        author=current_user.username,  # 后端从登录状态获取
        author_id=current_user.id,  # 后端从登录状态获取
        parent_id=comment.parent_id,  # 来自前端提交（可选）
        created_at=datetime.now()  # 后端生成时间
    )

    comments.append(new_comment)
    save_comments(comments)
    
    # 添加调试日志
    print(f"保存新评论: {new_comment.dict()}")
    print(f"评论总数: {len(comments)}")
    
    return new_comment

@app.put("/comments/{comment_id}", response_model=Comment)
def api_update_comment(
        comment_id: int,
        updated_comment: Comment,
        current_user: Optional[User] = Depends(get_current_user)
):
    if not current_user:
        raise HTTPException(status_code=401, detail="请先登录再修改评论")

    comments = load_comments()
    for idx, comment in enumerate(comments):
        if comment.id == comment_id:
            if comment.author_id != current_user.id:
                raise HTTPException(status_code=403, detail="没有权限修改他人的评论")

            updated_data = updated_comment.dict()
            updated_data["id"] = comment_id
            updated_data["author"] = comment.author
            updated_data["author_id"] = comment.author_id
            updated_data["created_at"] = comment.created_at

            comments[idx] = Comment(**updated_data)
            save_comments(comments)
            return comments[idx]

    raise HTTPException(status_code=404, detail="要修改的评论不存在")


@app.delete("/comments/{comment_id}", status_code=status.HTTP_204_NO_CONTENT)
def api_delete_comment(
        comment_id: int,
        current_user: Optional[User] = Depends(get_current_user)
):
    if not current_user:
        raise HTTPException(status_code=401, detail="请先登录再删除评论")

    comments = load_comments()
    target_comment = next((c for c in comments if c.id == comment_id), None)
    if not target_comment:
        raise HTTPException(status_code=404, detail="要删除的评论不存在")

    if target_comment.author_id != current_user.id:
        raise HTTPException(status_code=403, detail="没有权限删除他人的评论")

    filtered_comments = [
        c for c in comments
        if c.id != comment_id and c.parent_id != comment_id
    ]

    save_comments(filtered_comments)
    return None


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000, reload=True)